package com.lzs.lzscodesandbox;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.dfa.FoundWord;
import cn.hutool.dfa.WordTree;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.lzs.lzscodesandbox.model.ExecuteCodeRequest;
import com.lzs.lzscodesandbox.model.ExecuteCodeResponse;
import com.lzs.lzscodesandbox.model.ExecuteMessage;
import com.lzs.lzscodesandbox.model.JudgeInfo;
import com.lzs.lzscodesandbox.utils.ProcessUtils;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

public class JavaDockerCodeSandbox implements CodeSandbox {

    private static final String GLOBAL_CODE_DIR_NAME = "tmpCode";

    private static final String GLOBAL_JAVA_CLASS_NAME = "Main.java";

    private static final long TIME_OUT = 5000L;

    private static final boolean FIRST_INIT = true;



    public static void main(String[] args) {
        JavaDockerCodeSandbox javaNativeCodeSandbox = new JavaDockerCodeSandbox();
        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
        executeCodeRequest.setInputList(Arrays.asList("1 2", "3 4"));
        String code = ResourceUtil.readStr("testCode.simpleComputeArgs/Main.java", StandardCharsets.UTF_8);// 读取文件的代码
//        String code = ResourceUtil.readStr("testCode.simpleComputeArgs/unsafeCode/SleepError.java", StandardCharsets.UTF_8);// 读取文件的代码
//        String code = ResourceUtil.readStr("testCode.simpleComputeArgs/unsafeCode/ReadFileError.java", StandardCharsets.UTF_8);// 读取文件的代码
//        String code = ResourceUtil.readStr("testCode.simpleComputeArgs/unsafeCode/MemoryError.java", StandardCharsets.UTF_8);// 读取文件的代码
        executeCodeRequest.setCode(code);
        executeCodeRequest.setLanguage("java");
        ExecuteCodeResponse executeCodeResponse = javaNativeCodeSandbox.executeCode(executeCodeRequest);// 调用方法传入响应，输出返回结果
        System.out.println(executeCodeResponse);

    }

    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
        List<String> inputList = executeCodeRequest.getInputList();
        String code = executeCodeRequest.getCode();
        String language = executeCodeRequest.getLanguage();

        // 1.把用户的代码保存为文件
        String userDir = System.getProperty("user.dir");// 获取到项目的根目录
        String globalCodePAthName = userDir + File.separator + GLOBAL_CODE_DIR_NAME;// File.separator 用于匹配不同系统的 左（右）划线--> / \
        // 判断全局代码目录是否存在
        if (!FileUtil.exist(globalCodePAthName)) {
            FileUtil.mkdir(globalCodePAthName);// 不存在就新建
        }
        // 把用户代码隔离存放
        String userCodeParentPath = globalCodePAthName + File.separator + UUID.randomUUID();// 存放代码的文件夹目录
        // 实际代码文件Main.java
        String userCodePath = userCodeParentPath + File.separator + GLOBAL_JAVA_CLASS_NAME;
        // 将用户代码写入文件
        File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);

        // 2.编译代码，得到class文件
        String compileCmd = String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());// 代码所在文件（Main.java）的路径
        try {
            Process compileProcess = Runtime.getRuntime().exec(compileCmd);// 执行编译代码
            ExecuteMessage executeMessage = ProcessUtils.runProcessAndGetMessage(compileProcess, "编译");
            System.out.println(executeMessage);

        } catch (Exception e) {
            return getErrorResponse(e);
//            throw new RuntimeException(e);
        }

        // 3.创建容器。把文件复制到容器内
        // 获取默认的Docker Client
        DockerClient dockerClient = DockerClientBuilder.getInstance().build();

        // 拉取java镜像
        String image = "openjdk:8-alpine";// alpine轻量化版本
        if (FIRST_INIT){// 如果是第一次初始化，那就拉取镜像
            PullImageCmd pullImageCmd = dockerClient.pullImageCmd(image);
            PullImageResultCallback pullImageResultCallback = new PullImageResultCallback() {
                @Override
                public void onNext(PullResponseItem item) {
                    System.out.println("下载镜像：" + item.getStatus());
                    super.onNext(item);
                }
            };
            try {
                pullImageCmd
                        .exec(pullImageResultCallback)
                        .awaitCompletion();
            } catch (InterruptedException e) {
                System.out.println("拉取镜像异常");
                throw new RuntimeException(e);
            }
        }

        System.out.println("下载完成");
        // 创建容器
        CreateContainerCmd containerCmd = dockerClient.createContainerCmd(image);
        HostConfig hostConfig = new HostConfig();
        hostConfig.withMemory(100 * 1000 * 1000L);// 设置内存
        hostConfig.withCpuCount(1L);// 设置CPU
        hostConfig.setBinds(new Bind(userCodeParentPath,new Volume("/app/ ")));// (主机文件路径，容器内部文件路径)
        CreateContainerResponse createContainerResponse = containerCmd
                .withHostConfig(hostConfig)
                .withAttachStdin(true)
                .withAttachStderr(true)
                .withAttachStdout(true)// docker容器和本地终端进行连接，获取输入并在终端得到输出
                .withTty(true)// 交互终端
                .exec();
        System.out.println(createContainerResponse);
        String containerId = createContainerResponse.getId();// 容器实例id

        // 启动容器
        dockerClient.startContainerCmd(containerId).exec();

        for (String inputArgs : inputList) {
            String[] inputArgsArray = inputArgs.split(" ");
            String[] cmdArray = ArrayUtil.append(new String[]{"java", "-cp", "/app", "Main"}, inputArgsArray);
            ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
                    .withCmd(cmdArray)
                    .withAttachStderr(true)
                    .withAttachStdin(true)
                    .withAttachStdout(true)
                    .exec();
            System.out.println("创建执行命令：" + execCreateCmdResponse);
            String execId = execCreateCmdResponse.getId();
            ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback(){// 定义回调

                @Override
                public void onNext(Frame frame) {
                    StreamType streamType = frame.getStreamType();
                    if (streamType.STDERR.equals(streamType)){
                        System.out.println("输出错误结果：" + new String(frame.getPayload()));
                    }else {
                        System.out.println("输出结果：" + new String(frame.getPayload()));
                    }
                    super.onNext(frame);
                }
            };
            try {
                dockerClient.execStartCmd(execId)
                        .exec(execStartResultCallback)
                        .awaitCompletion(); // 启动命令参数为创建的id
            } catch (InterruptedException e) {
                System.out.println("程序执行异常");
                throw new RuntimeException(e);
            }
        }


        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        return executeCodeResponse;

    }

    /**
     * 获取错误响应
     * @param e
     * @return
     */
    // 创建方法，判断错误处理（错误处理前有内部异常处理）
    private ExecuteCodeResponse getErrorResponse(Throwable e){
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        executeCodeResponse.setOutputList(new ArrayList<>());
        executeCodeResponse.setMessage(e.getMessage());
        // 表示代码沙箱错误
        executeCodeResponse.setStatus(2);
        executeCodeResponse.setJudgeInfo(new JudgeInfo());
        return executeCodeResponse;
    }

}
